{
  Copyright 2012 Sergey Ostanin

  Licensed under the Apache License, Version 2.0 (the "License");
  you may not use this file except in compliance with the License.
  You may obtain a copy of the License at

      http://www.apache.org/licenses/LICENSE-2.0

  Unless required by applicable law or agreed to in writing, software
  distributed under the License is distributed on an "AS IS" BASIS,
  WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
  See the License for the specific language governing permissions and
  limitations under the License.
}

unit ClassifyQuestionEditor;

interface

uses
  Classes, SysUtils, FileUtil, Forms, Controls, Graphics, Dialogs, ExtCtrls,
  ComCtrls, ActnList, Shape, PadEditor, VisualUtils, superobject, MiscUtils,
  Math, ClassifyQuestionOptions, QuestionEditors, Questions, ClassifyQuestion,
  Dispatcher;

type
  TClassifyQuestionEditorFrame = class(TShapeFrame, IQuestionEditor)
    actAddCategory: TAction;
    actAddItem: TAction;
    actDeleteElement: TAction;
    actSetOptions: TAction;
    actMoveElementDown: TAction;
    actMoveElementUp: TAction;
    alQuestion: TActionList;
    ilQuestion: TImageList;
    pnlFormulation: TPanel;
    pnlQuestion: TPanel;
    sbxQuestion: TScrollBox;
    tbrQuestion: TToolBar;
    tbAddCategory: TToolButton;
    tbAddItem: TToolButton;
    tbDeleteElement: TToolButton;
    tbSeparator1: TToolButton;
    tbMoveElementUp: TToolButton;
    tbMoveElementDown: TToolButton;
    tbSeparator2: TToolButton;
    tbSetOptions: TToolButton;
    procedure actAddCategoryExecute(Sender: TObject);
    procedure actAddItemExecute(Sender: TObject);
    procedure actAddItemUpdate(Sender: TObject);
    procedure actDeleteElementExecute(Sender: TObject);
    procedure actDeleteElementUpdate(Sender: TObject);
    procedure actMoveElementDownExecute(Sender: TObject);
    procedure actMoveElementDownUpdate(Sender: TObject);
    procedure actMoveElementUpExecute(Sender: TObject);
    procedure actMoveElementUpUpdate(Sender: TObject);
    procedure actSetOptionsExecute(Sender: TObject);
  private type
    TElementFrame = class(TPadEditorFrame)
    private
      FCategoryIndex: Integer;
      FItemIndex: Integer;
      FCanMoveUp: Boolean;
      FCanMoveDown: Boolean;
    public
      constructor Create(const Element: ISuperObject; Dispatcher: TDispatcher;
        Display: TWinControl); reintroduce;
      function IsCategory: Boolean;
      procedure Render;
    end;
    TElementFrameList = TGenericObjectList<TElementFrame>;
  private
    FFormulation: TPadEditorFrame;
    FElements: TElementFrameList;
    FOldElementEditorIds: UnicodeString;
    FSelectElement: Integer;
    FCanAddItem: Boolean;
    FOptionsImageIndex: Integer;
    FCustomOptionsImageIndex: Integer;
    procedure SyncElementEditors;
    function SelectedElementIndex: Integer;
    procedure MoveElement(Down: Boolean);
    { private declarations }
  public
    procedure AfterConstruction; override;
    destructor Destroy; override;
    procedure Render;
    { public declarations }
  end; 

implementation

{$R *.lfm}
{$R *.rc}

resourcestring
  SClassifyQuestion = 'Classification Question';
  SInitialText = 'Classify the items.';

{ TClassifyQuestionEditorFrame.TElementFrame }

constructor TClassifyQuestionEditorFrame.TElementFrame.Create(const Element: ISuperObject;
  Dispatcher: TDispatcher; Display: TWinControl);
var
  ItemIndex: ISuperObject;
begin
  inherited Create(Element.S['editor'], Dispatcher, Display);
  FCategoryIndex := Element.I['categoryIndex'];
  ItemIndex := Element['itemIndex'];
  if ItemIndex = nil then
    FItemIndex := -1
  else
    FItemIndex := ItemIndex.AsInteger;
  FCanMoveUp := Element.B['canMoveUp'];
  FCanMoveDown := Element.B['canMoveDown'];

  if not IsCategory then
    ChildSizing.LeftRightSpacing := 20;
end;

function TClassifyQuestionEditorFrame.TElementFrame.IsCategory: Boolean;
begin
  Result := FItemIndex = -1;
end;

procedure TClassifyQuestionEditorFrame.TElementFrame.Render;
begin
  inherited;
  if IsCategory then
    PadColor := $d0d0d0;
end;

{ TClassifyQuestionEditorFrame }

procedure TClassifyQuestionEditorFrame.actAddCategoryExecute(Sender: TObject);
begin
  Dispatcher.PerformAction(Id, 'addCategory');
  FSelectElement := FElements.Count;
end;

procedure TClassifyQuestionEditorFrame.actAddItemExecute(Sender: TObject);
var
  Input: ISuperObject;
  Index, CategoryIndex: Integer;
begin
  Index := SelectedElementIndex;
  CategoryIndex := FElements[Index].FCategoryIndex;

  Input := SO;
  Input.I['categoryIndex'] := CategoryIndex;
  Dispatcher.PerformAction(Id, 'addItem', Input);

  repeat
    Inc(Index);
  until (Index >= FElements.Count) or (FElements[Index].FCategoryIndex <> CategoryIndex);
  FSelectElement := Index;
end;

procedure TClassifyQuestionEditorFrame.actAddItemUpdate(Sender: TObject);
begin
  (Sender as TAction).Enabled := FCanAddItem and (SelectedElementIndex <> -1);
end;

procedure TClassifyQuestionEditorFrame.actDeleteElementExecute(Sender: TObject);
var
  Index: Integer;
  Element: TElementFrame;
  Input: ISuperObject;
begin
  Index := SelectedElementIndex;
  Element := FElements[Index];
  Input := SO;
  if Element.IsCategory then
  begin
    Input.I['index'] := Element.FCategoryIndex;
    Dispatcher.PerformAction(Id, 'deleteCategory', Input);
  end
  else
  begin
    Input.I['categoryIndex'] := Element.FCategoryIndex;
    Input.I['itemIndex'] := Element.FItemIndex;
    Dispatcher.PerformAction(Id, 'deleteItem', Input);
  end;

  FSelectElement := Index;
end;

procedure TClassifyQuestionEditorFrame.actDeleteElementUpdate(Sender: TObject);
begin
  (Sender as TAction).Enabled := SelectedElementIndex <> -1;
end;

procedure TClassifyQuestionEditorFrame.actMoveElementDownExecute(Sender: TObject);
begin
  MoveElement(TRUE);
end;

procedure TClassifyQuestionEditorFrame.actMoveElementDownUpdate(Sender: TObject);
var
  Index: Integer;
begin
  Index := SelectedElementIndex;
  (Sender as TAction).Enabled := (Index <> -1) and FElements[Index].FCanMoveDown;
end;

procedure TClassifyQuestionEditorFrame.actMoveElementUpExecute(Sender: TObject);
begin
  MoveElement(FALSE);
end;

procedure TClassifyQuestionEditorFrame.actMoveElementUpUpdate(Sender: TObject);
var
  Index: Integer;
begin
  Index := SelectedElementIndex;
  (Sender as TAction).Enabled := (Index <> -1) and FElements[Index].FCanMoveUp;
end;

procedure TClassifyQuestionEditorFrame.actSetOptionsExecute(Sender: TObject);
begin
  TClassifyQuestionOptionsForm.OpenModal(Id, Shape.Clone, Dispatcher);
end;

procedure TClassifyQuestionEditorFrame.SyncElementEditors;
var
  Elements, Element: ISuperObject;
begin
  Elements := Shape['element'];
  if AssignDiffering(Elements.AsJSon, FOldElementEditorIds) then
  begin
    FElements.Clear;
    for Element in Elements do
      FElements.AddSafely(TElementFrame.Create(Element, Dispatcher, pnlQuestion));
  end;
end;

function TClassifyQuestionEditorFrame.SelectedElementIndex: Integer;
begin
  for Result := 0 to FElements.Count-1 do
    if ContainsActiveFormControl(FElements[Result]) then
      Exit;
  Result := -1;
end;

procedure TClassifyQuestionEditorFrame.MoveElement(Down: Boolean);
var
  Element: TElementFrame;
  Input: ISuperObject;
begin
  Element := FElements[SelectedElementIndex];
  Input := SO;
  Input.B['forward'] := Down;
  if Element.IsCategory then
  begin
    Input.I['index'] := Element.FCategoryIndex;
    Dispatcher.PerformAction(Id, 'moveCategory', Input);
  end
  else
  begin
    Input.I['categoryIndex'] := Element.FCategoryIndex;
    Input.I['itemIndex'] := Element.FItemIndex;
    Dispatcher.PerformAction(Id, 'moveItem', Input);
  end;
end;

procedure TClassifyQuestionEditorFrame.AfterConstruction;
begin
  inherited;
  alQuestion.OnExecute := Scene.ActionListExecuteFilter;
  LoadActionImages(alQuestion);
  MakeVerticallyAutoScrollable(sbxQuestion);

  FFormulation := TPadEditorFrame.Create(Shape.S['formulationEditor'], Dispatcher, pnlFormulation);
  FFormulation.Align := alClient;
  FFormulation.Border := TRUE;

  FElements := TElementFrameList.Create;
  FSelectElement := -1;

  FOptionsImageIndex := AddPngResourceToImageList('ClassifyQuestionEditor/options.png', ilQuestion);
  FCustomOptionsImageIndex := AddPngResourceToImageList('ClassifyQuestionEditor/customOptions.png', ilQuestion);
end;

destructor TClassifyQuestionEditorFrame.Destroy;
begin
  FreeAndNil(FFormulation);
  FreeAndNil(FElements);
  inherited;
end;

procedure TClassifyQuestionEditorFrame.Render;
var
  Element: TElementFrame;
begin
  UpdateShape;

  DisableAutoSizing;
  try
    FFormulation.Render;
    SyncElementEditors;
    for Element in FElements do
      Element.Render;
  finally
    EnableAutoSizing;
  end;

  FSelectElement := Min(FSelectElement, FElements.Count-1);
  if FSelectElement >= 0 then
    FElements[FSelectElement].SelectFirst;
  FSelectElement := -1;

  actAddCategory.Enabled := Shape.B['canAddCategory'];
  FCanAddItem := Shape.B['canAddItem'];

  if Shape.B['hasDefaultOptions'] then
    actSetOptions.ImageIndex := FOptionsImageIndex
  else
    actSetOptions.ImageIndex := FCustomOptionsImageIndex;
end;

initialization

  QuestionRegistry.Add(TClassifyQuestion.Kind, SClassifyQuestion, 'ClassifyQuestionEditor/classifyQuestion.png', SInitialText);
  QuestionEditorRegistry.Add(TClassifyQuestionEditorFrame, $a5fa42972718b996);

end.

